热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

也就是|笔者_yaff2util的解析1mkyaffs2工具

篇首语:本文由编程笔记#小编为大家整理,主要介绍了yaff2util的解析1--mkyaffs2工具相关的知识,希望对你有一定的参考价值。下载源码

篇首语:本文由编程笔记#小编为大家整理,主要介绍了yaff2util的解析1--mkyaffs2工具相关的知识,希望对你有一定的参考价值。



下载源码

目前在网络上还是以google提供的为主,其他的看上去好像不维护的样子,当然如果有读者看到的话,请帮忙提供一下。比这目前参考的是gitee上同步下来的code.google.com/p/yaffs2utils。目前笔者的VPN到期,只能拿别人的代码来分析了。当然放心,分析完,我也将源码附上。


编译

编译还是超级简单的。直接在项目根目录下make all即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一般来说会生成3个重要的tool,分别是mkyaffs2、unspare2、unyaffs2


mkyaffs2的工具解析

先来看一下Makefile是杂么编译的。

mkyaffs2: $(YAFFS2OBJS) $(LIBOBJS) $(MKYAFFS2OBJS)
$(CC) -o $@ $(YAFFS2OBJS) $(LIBOBJS) $(MKYAFFS2OBJS) $(LDFLAGS)

其需要依赖:

YAFFS2SRCS = yaffs2/yaffs_hweight.c yaffs2/yaffs_ecc.c \\
yaffs2/yaffs_packedtags1.c yaffs2/yaffs_packedtags2.c
YAFFS2OBJS = $(YAFFS2SRCS:.c=.o)
LIBSRCS = safe_rw.c endian_convert.c progress_bar.c
LIBOBJS = $(LIBSRCS:.c=.o)
MKYAFFS2SRCS = mkyaffs2.c
MKYAFFS2OBJS = $(MKYAFFS2SRCS:.c=.o)
LDFLAGS += -lm

于是基本上可以确认其主要依赖的一些C文件也就出来了,分别是yaffs_hweight.c、 yaffs_ecc.c、yaffs_packedtags1.c、yaffs_packedtags2.c、safe_rw.c、endian_convert.c、progress_bar.c、mkyaffs2.c


main函数

先可以看到getopt_long的一些定义。

static const char *short_options = "hvep:s:o:";
static const struct option long_options[] =
"pagesize", required_argument, 0, 'p',
"sparesize", required_argument, 0, 's',
"oobimg", required_argument, 0, 'o',
"endian", no_argument, 0, 'e',
"verbose", no_argument, 0, 'v',
"all-root", no_argument, 0, '0',
"yaffs-ecclayout", no_argument, 0, 'y',
"help", no_argument, 0, 'h',
NULL, no_argument, 0, '\\0',
;

这边先复习一下getopt_long


getopt_long


#include
extern char *optarg;
extern int optind, opterr, optopt;
#include
int getopt(int argc, char * const argv[],const char *optstring);
int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);

1.argc和argv和main函数的两个参数一致。

2.optstring: 表示短选项字符串

形式如a🅱️💿,分别表示程序支持的命令行短选项有-a、-b、-c、-d,冒号含义如下:
(1)只有一个字符,不带冒号——只表示选项, 如-c
(2)一个字符,后接一个冒号——表示选项后面带一个参数,如-a 100
(3)一个字符,后接两个冒号——表示选项后面带一个可选参数,即参数可有可无,如果带参数,则选项与参数直接不能有空格如-b200


3.longopts 表示长选项结构体。

struct option

const char *name;
int has_arg;
int *flag;
int val;
;

(1)name:表示选项的名称,比如daemon,dir,out等。

(2)has_arg:表示选项后面是否携带参数。该参数有三个不同值,如下:

a: no_argument(或者是0)时 ——参数后面不跟参数值,eg: --version,–help
b: required_argument(或者是1)时 ——参数输入格式为:–参数 值 或者 --参数=值。eg:–dir=/home
c: optional_argument(或者是2)时 ——参数输入格式只能为:–参数=值

(3)flag:这个参数有两个意思,空或者非空。

a:如果参数为空NULL,那么当选中某个长选项的时候,getopt_long将返回val值。eg:可执行程序 --help,getopt_long的返回值为h.
b:如果参数不为空,那么当选中某个长选项的时候,getopt_long将返回0,并且将flag指针参数指向val值。eg: 可执行程序 --http-proxy=127.0.0.1:80 那么getopt_long返回值为0,并且lopt值为1。

(4)val:表示指定函数找到该选项时的返回值,或者当flag非空时指定flag指向的数据的值val。


4.longindex:longindex非空,它指向的变量将记录当前找到参数符合longopts里的第几个元素的描述,即是longopts的下标值。

5.全局变量:

(1)optarg:表示当前选项对应的参数值。

(2)optind:表示的是下一个将被处理到的参数在argv中的下标值。

(3)opterr:如果opterr = 0,在getopt、getopt_long、getopt_long_only遇到错误将不会输出错误信息到标准输出流。opterr在非0时,向屏幕输出错误。

(4)optopt:表示没有被未标识的选项。


6.返回值:

(1)如果短选项找到,那么将返回短选项对应的字符。

(2)如果长选项找到,如果flag为NULL,返回val。如果flag不为空,返回0

(3)如果遇到一个选项没有在短字符、长字符里面。或者在长字符里面存在二义性的,返回“?”

(4)如果解析完所有字符没有找到(一般是输入命令参数格式错误,eg: 连斜杠都没有加的选项),返回“-1”

(5)如果选项需要参数,忘了添加参数。返回值取决于optstring,如果其第一个字符是“:”,则返回“:”,否则返回“?”。


参数的使用

OK,现在主要搞清楚了一些参数配置问题哦,那我们来解释一下参数。首先我们来看一下美光的MT29F1G的128MB的flash。

在yaffs2中把基本的存储单位称为chunk,其实它跟page是一样大小的,在大多数情况下其和page就是一个意思。那么这边的参数就要注意了,千万不要将这个128bytes算进chunk去,我们的chunk一直都是2K=2048,128字节是缓存区。

mkyaffs2_chunksize = DEFAULT_CHUNKSIZE;

DEFAULT_CHUNKSIZE就是2048,所以在这边就用配置-p这个选项了。


1.先检查参数够不够

if (argc - optind < 2)
return mkyaffs2_helper();

一定要有一个文件目录&#xff0c;一个image的名字


2.检查是否有root权限

if (getuid() !&#61; 0)
mkyaffs2_flags |&#61; MKYAFFS2_FLAGS_NONROOT;
MKYAFFS2_WARN("warning: non-root users.\\n");


3.是否有oob分区

有些平台需要刷入带OOB的外置分区&#xff0c;也就是ECC的分区&#xff0c;特别是各个平台上有所不同&#xff0c;这块的话&#xff0c;后续遇到的话&#xff0c;我将在这个地方继续更新一下。

那么-o也就不需要我们来关心


4.oob layout配置

在这边如果没有特殊的要求&#xff0c;也就是要使用yaffs2的ecclayout,一般我们默认使用nand的oob

switch (mkyaffs2_chunksize)
case 512:
mkyaffs2_flags |&#61; MKYAFFS2_FLAGS_YAFFS1;
mkyaffs2_assemble_ptags &#61; &mkyaffs2_assemble_ptags1;
if (oobfile &#61;&#61; NULL)
mkyaffs2_ecclayout &#61; &nand_oob_16;
break;
case 2048:
if (mkyaffs2_ecclayout &#61;&#61; NULL)
mkyaffs2_ecclayout &#61; MKYAFFS2_ISYAFFSECC ?
&yaffs_nand_oob_64 : &nand_oob_64;
break;
...

直接选择的是2048也就是2K&#xff0c;那么此时你需要关注的是nand_ecclayout.h

static nand_ecclayout_t nand_oob_64 &#61;
.eccbytes &#61; 24,
.eccpos &#61; 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63,
.oobfree &#61; .offset &#61; 2, .length &#61; 38,
;
static nand_ecclayout_t yaffs_nand_oob_64 &#61;
.eccbytes &#61; 24,
.eccpos &#61; 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63,
.oobfree &#61; .offset &#61; 0, .length &#61; 40,
;

很明显 -y在默认情况下&#xff0c;我们也不会使用


检查交换空间

mkyaffs2_sparesize这个笔者自己也不清楚是干什么用的&#xff0c;不过看上去像是交换空间&#xff0c;对于美光来讲只有128个字节&#xff0c;也就是前面所说的缓冲区128字节。看来-s是要用上了&#xff0c;一般默认的交换空间是chunksize的32分之一&#xff0c;而美光的是128.当然这边我不太确定&#xff0c;我这边理解的对不对。


mkyaffs2_create_image

这个函数是制作image的关键。下面就是要分析这个函数具体又是如何工作的。

函数原型如下&#xff1a;

static int mkyaffs2_create_image (const char *dirpath, const char *imgfile)


检查参数

if (stat(dirpath, &statbuf) <0 && !S_ISDIR(statbuf.st_mode))
MKYAFFS2_ERROR("ROOT is not a directory &#39;%s&#39;.\\n", dirpath);
return -1;

在这边其实还是很学到不少东西的。stat(dirpath, &statbuf)将目录文件路径放到stat结构体里面&#xff0c;然后判断这个inode是不是目录。用stat函数来检查文件路径是否为目录或者类似于块设备&#xff0c;字符设备等等&#xff0c;这点是以后笔者需要学习的地方。

这边有个博客写得还行。介绍了这个。


allocate root obj first 建立根目录

其代码为&#xff1a;

/* allocate root obj first */
root &#61; mkyaffs2_obj_alloc();
if (root &#61;&#61; NULL)
MKYAFFS2_ERROR("allocate object failed for &#39;%s&#39;: %s.\\n",
dirpath, strerror(errno));
return -1;

我们看其源码&#xff1a;

static struct mkyaffs2_obj *
mkyaffs2_obj_alloc (void)

struct mkyaffs2_obj *obj;
obj &#61; calloc(sizeof(struct mkyaffs2_obj), sizeof(unsigned char));//初始化内存
if (obj &#61;&#61; NULL)
return NULL;
obj->parent_obj &#61; obj;
INIT_LIST_HEAD(&obj->hashlist);
INIT_LIST_HEAD(&obj->children);
INIT_LIST_HEAD(&obj->siblings);
return obj;

其mkyaffs2_obj结构体是我们要关注的。先贴出&#xff0c;后面一点点分析

typedef struct mkyaffs2_obj
dev_t dev;
ino_t ino;
unsigned obj_id;
struct mkyaffs2_obj *parent_obj;
unsigned type;
char name[NAME_MAX &#43; 1];
struct list_head children; /* for a directory */
struct list_head siblings; /* neighbors in the same directory */
struct list_head hashlist; /* hash table */
mkyaffs2_obj_t;

mkyaffs2_obj_alloc这边已经将数据列表全部初始化&#xff0c;INIT_LIST_HEAD将所有的链表都指向于自己。其实只要对于内核有一定认知的&#xff0c;都会有各种指向于列表本身嘛。


table initiailzation

其主要是这两个函数做文件序列

mkyaffs2_objtable_init();
mkyaffs2_objtree_init2(&mkyaffs2_objtree, root);

静态全局变量与相关的结构体

typedef struct mkyaffs2_fstree
unsigned objs;
struct mkyaffs2_obj *root;
mkyaffs2_fstree_t;
static struct mkyaffs2_fstree mkyaffs2_objtree &#61; 0;
static struct list_head mkyaffs2_objtable[MKYAFFS2_OBJTABLE_SIZE];

mkyaffs2_objtable_init主要初始化mkyaffs2_objtable

static inline void
mkyaffs2_objtable_init (void)

unsigned n;
for (n &#61; 0; n < MKYAFFS2_OBJTABLE_SIZE; n&#43;&#43;)
INIT_LIST_HEAD(&mkyaffs2_objtable[n]);

mkyaffs2_objtree_init2主要先把全局变量初始化后&#xff0c;给他们相关的内存空间&#xff0c;并绑定根目录

static struct mkyaffs2_fstree *
mkyaffs2_objtree_init (struct mkyaffs2_fstree *fst)

struct mkyaffs2_fstree *f &#61; fst;
if (f &#61;&#61; NULL)
f &#61; malloc(sizeof(struct mkyaffs2_fstree));
if (f &#61;&#61; NULL)
return NULL;

/* initialize */
memset(f, 0, sizeof(struct mkyaffs2_fstree));
return f;

static struct mkyaffs2_fstree *
mkyaffs2_objtree_init2 (struct mkyaffs2_fstree *fst, struct mkyaffs2_obj *root)

struct mkyaffs2_fstree *f &#61; fst;
f &#61; mkyaffs2_objtree_init(f);
if (f && root)
f->root &#61; root;
f->objs&#43;&#43;;

return f;


allocate working buffer

下面就是要提供一个buffer用于写数据了

mkyaffs2_bufsize &#61; mkyaffs2_chunksize &#43; mkyaffs2_sparesize;
mkyaffs2_databuf &#61; (unsigned char *)malloc(mkyaffs2_bufsize);
if (mkyaffs2_databuf &#61;&#61; NULL)
MKYAFFS2_ERROR("cannot allocate working buffer (%u bytes): %s",
mkyaffs2_chunksize &#43; mkyaffs2_sparesize,
strerror(errno));
retval &#61; -1;
goto exit_and_out;


mkyaffs2_image_fd &#61; open(imgfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);//打开我们要生成的image.bin
if (mkyaffs2_image_fd <0)
MKYAFFS2_ERROR("cannot open the image file: &#39;%s&#39;.\\n", imgfile);
retval &#61; -1;
goto free_and_out;

后面开始正式创建yaffs2了


stage 1: scanning direcotry

snprintf(mkyaffs2_curfile, PATH_MAX, "%s", dirpath);
MKYAFFS2_PRINTF("\\n");
MKYAFFS2_PRINTF("stage 1: scanning directory &#39;%s&#39;... [*]",mkyaffs2_curfile);
retval &#61; mkyaffs2_scan_dir(mkyaffs2_objtree.root);//开始扫描整个待包入的文件夹
if (retval < 0)
goto free_and_out;
MKYAFFS2_PRINTF("\\b\\b\\b[done]\\nscanning complete, total objects: %u.\\n",
mkyaffs2_objtree.objs);

其主要的函数是mkyaffs2_scan_dir

static int mkyaffs2_scan_dir (struct mkyaffs2_obj *parent)

int retval &#61; 0;
DIR *dir;
struct stat s;
struct dirent *dent;
struct mkyaffs2_obj *obj &#61; NULL;
dir &#61; opendir(mkyaffs2_curfile[0] &#61;&#61; &#39;\\0&#39; ? "." : mkyaffs2_curfile);//这边如果你没有输入文件名的话&#xff0c;是直接在当前目录下
if (dir &#61;&#61; NULL)
MKYAFFS2_ERROR("cannot open dir &#39;%s&#39;: %s.",
mkyaffs2_curfile, strerror(errno));
return -1;

while(!retval && (dent &#61; readdir(dir)) !&#61; NULL)
if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))//这边是当前目录和上级目录是跳过
continue;
if (mkyaffs2_curfile[0] !&#61; &#39;\\0&#39; && mkyaffs2_curfile[strlen(mkyaffs2_curfile) - 1] !&#61; &#39;/&#39;)//这边是看是否有目录名的&#xff0c;且最后一个目录名没有写“/“这里直接追加。
strncat(mkyaffs2_curfile, "/",
sizeof(mkyaffs2_curfile) -
strlen(mkyaffs2_curfile) - 1);
strncat(mkyaffs2_curfile, dent->d_name,
sizeof(mkyaffs2_curfile) - strlen(mkyaffs2_curfile) - 1);
obj &#61; mkyaffs2_obj_alloc();
if (obj &#61;&#61; NULL)
MKYAFFS2_ERROR("allocate object failed for &#39;%s&#39;: %sn\\n",
mkyaffs2_curfile, strerror(errno));
return -1;

strncpy(obj->name, dent->d_name, NAME_MAX);
obj->parent_obj &#61; parent;
list_add_tail(&obj->siblings, &parent->children);//建立列表
mkyaffs2_scan_dir_status(&#43;&#43;mkyaffs2_objtree.objs);
if (!lstat(mkyaffs2_curfile, &s) && S_ISDIR(s.st_mode))
retval &#61; mkyaffs2_scan_dir(obj);
if (!strcmp(dirname(mkyaffs2_curfile), "."))
mkyaffs2_curfile[0] &#61; &#39;\\0&#39;;

closedir(dir);
return retval;


stage 2: making a image

MKYAFFS2_PRINTF("\\n");
MKYAFFS2_PRINTF("stage 2: creating image &#39;%s&#39;\\n", imgfile);
MKYAFFS2_PROGRESS_INIT();
snprintf(mkyaffs2_curfile, PATH_MAX, "%s", dirpath);
retval &#61; mkyaffs2_assemble_objtree(mkyaffs2_objtree.root);

这边最要紧的还是mkyaffs2_assemble_objtree这个函数&#xff0c;这个函数的套路大约是自己再调自己直到把目录内的所有文件转换成obj&#xff0c;然后调用mkyaffs2_write_obj&#xff0c;随后调用mkyaffs2_write_oh将数据写到mkyaffs2_databuf中&#xff0c;注意哦&#xff0c;这边的内存部分是直接赋值0xff的&#xff0c;为撒&#xff0c;flash空数据时就是全ff的状态&#xff0c;最后调用mkyaffs2_write_chunk的safe_write&#xff0c;将数据写到image.bin中。

static int
mkyaffs2_assemble_objtree (struct mkyaffs2_obj *obj)

int retval &#61; 0;
struct stat s;
struct list_head *p;
struct mkyaffs2_obj *child;
enum yaffs_obj_type type &#61; YAFFS_OBJECT_TYPE_UNKNOWN;
static const char *type_str[] &#61; "????", "FILE", "SLNK", "DIR", "HLNK",
"CHR", "BLK", "FIFO", "SOCK";
/* root object *///检查根目录是否合规
if (obj &#61;&#61; mkyaffs2_objtree.root)
if (stat(mkyaffs2_curfile[0] &#61;&#61; &#39;\\0&#39; ?
"." : mkyaffs2_curfile, &s) < 0 ||
!S_ISDIR(s.st_mode))
MKYAFFS2_ERROR("ROOT is NOT a directory &#39;%s&#39; "
"(permission denied?)\\n",
mkyaffs2_curfile);
return -1;

obj->dev &#61; s.st_dev;
obj->ino &#61; s.st_ino;
obj->obj_id &#61; YAFFS_OBJECTID_ROOT;
obj->type &#61; YAFFS_OBJECT_TYPE_DIRECTORY;
mkyaffs2_objtable_insert(obj);//加入文件表内
goto next;

if (!strlen(obj->name))
/* it should NOT happen! */
MKYAFFS2_DEBUG("skip obj with empty name\\n");
return 0;

/* format file path */
if (mkyaffs2_curfile[0] !&#61; &#39;\\0&#39; &&
mkyaffs2_curfile[strlen(mkyaffs2_curfile) - 1] !&#61; &#39;/&#39;)
strncat(mkyaffs2_curfile, "/",
sizeof(mkyaffs2_curfile) -
strlen(mkyaffs2_curfile) - 1);
strncat(mkyaffs2_curfile, obj->name,
sizeof(mkyaffs2_curfile) - strlen(mkyaffs2_curfile) - 1);
MKYAFFS2_VERBOSE("NOW: &#39;%s&#39;. ", mkyaffs2_curfile);
retval &#61; mkyaffs2_write_obj(mkyaffs2_curfile, obj);
if (!retval &&
obj->type !&#61; YAFFS_OBJECT_TYPE_HARDLINK &&
obj->type !&#61; YAFFS_OBJECT_TYPE_UNKNOWN)
mkyaffs2_objtable_insert(obj);
next:
if (retval)
MKYAFFS2_ERROR("object %u: [%4s] &#39;%s&#39; (FAILED).\\n",
obj->obj_id, type_str[type], mkyaffs2_curfile);

else
mkyaffs2_image_objs&#43;&#43;;

推荐阅读
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了在Vue项目中如何结合Element UI解决连续上传多张图片及图片编辑的问题。作者强调了在编码前要明确需求和所需要的结果,并详细描述了自己的代码实现过程。 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 阿里云虚拟主机安装多个织梦系统的方法
    本文介绍了在阿里云虚拟主机上安装多个织梦系统的方法。通过创建不同名称的文件夹并将不同的域名解析到对应的目录,可以实现多个系统的安装。在安装过程中需要注意修改数据库前缀,并在系统设置中还原数据库。同时还介绍了阿里云虚拟主机二级域名绑定二级目录和域名重定向的用法。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • MVC设计模式的介绍和演化过程
    本文介绍了MVC设计模式的基本概念和原理,以及在实际项目中的演化过程。通过分离视图、模型和控制器,实现了代码的解耦和重用,提高了项目的可维护性和可扩展性。详细讲解了分离视图、分离模型和分离控制器的具体步骤和规则,以及它们在项目中的应用。同时,还介绍了基础模型的封装和控制器的命名规则。该文章适合对MVC设计模式感兴趣的读者阅读和学习。 ... [详细]
  • 本文介绍了一个免费的asp.net控件,该控件具备数据显示、录入、更新、删除等功能。它比datagrid更易用、更实用,同时具备多种功能,例如属性设置、数据排序、字段类型格式化显示、密码字段支持、图像字段上传和生成缩略图等。此外,它还提供了数据验证、日期选择器、数字选择器等功能,以及防止注入攻击、非本页提交和自动分页技术等安全性和性能优化功能。最后,该控件还支持字段值合计和数据导出功能。总之,该控件功能强大且免费,适用于asp.net开发。 ... [详细]
author-avatar
祖巧爽_940
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有